/*
 * Copyright 2012-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.context.config;

import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.Name;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.util.ObjectUtils;

/**
 * 用于处理 {@link ConfigData} 时绑定的属性。
 *
 * @author Phillip Webb
 * @author Madhura Bhave
 */
class ConfigDataProperties {

	private static final ConfigurationPropertyName NAME = ConfigurationPropertyName.of("spring.config");

	private static final ConfigurationPropertyName LEGACY_PROFILES_NAME = ConfigurationPropertyName
			.of("spring.profiles");

	private static final Bindable<ConfigDataProperties> BINDABLE_PROPERTIES = Bindable.of(ConfigDataProperties.class);

	private static final Bindable<String[]> BINDABLE_STRING_ARRAY = Bindable.of(String[].class);

	private final List<ConfigDataLocation> imports;

	private final Activate activate;

	/**
	 * 创建一个新的 {@link ConfigDataProperties} 实例。
	 * @param imports 请求的导入列表
	 * @param activate 激活属性
	 */
	ConfigDataProperties(@Name("import") List<ConfigDataLocation> imports, Activate activate) {
		this.imports = (imports != null) ? imports : Collections.emptyList();
		this.activate = activate;
	}

	/**
	 * 返回请求的附加导入。
	 * @return 请求的导入列表
	 */
	List<ConfigDataLocation> getImports() {
		return this.imports;
	}

	/**
	 * 如果属性表示该配置数据属性源对于给定的激活上下文是激活的，则返回 {@code true}。
	 * @param activationContext 激活上下文
	 * @return 如果配置数据属性源是激活的，则返回 {@code true}
	 */
	boolean isActive(ConfigDataActivationContext activationContext) {
		return this.activate == null || this.activate.isActive(activationContext);
	}

	/**
	 * 返回一个不包含任何导入的新属性变体。
	 * @return 一个新的 {@link ConfigDataProperties} 实例
	 */
	ConfigDataProperties withoutImports() {
		return new ConfigDataProperties(null, this.activate);
	}

	ConfigDataProperties withLegacyProfiles(String[] legacyProfiles, ConfigurationProperty property) {
		if (this.activate != null && !ObjectUtils.isEmpty(this.activate.onProfile)) {
			throw new InvalidConfigDataPropertyException(property, false, NAME.append("activate.on-profile"), null);
		}
		return new ConfigDataProperties(this.imports, new Activate(this.activate.onCloudPlatform, legacyProfiles));
	}

	/**
	 * 工厂方法，用于从给定的 {@link Binder} 创建 {@link ConfigDataProperties}。
	 * @param binder 用于绑定属性的绑定器
	 * @return {@link ConfigDataProperties} 实例或 {@code null}
	 */
	static ConfigDataProperties get(Binder binder) {
		// 🧰 创建用于处理 旧版 profile 配置 的绑定处理器
		LegacyProfilesBindHandler legacyProfilesBindHandler = new LegacyProfilesBindHandler();

		// 📑 绑定 旧版 profile 配置（spring.profiles）为字符串数组
		String[] legacyProfiles = binder.bind(LEGACY_PROFILES_NAME, BINDABLE_STRING_ARRAY, legacyProfilesBindHandler)
				.orElse(null);

		// ⚙️ 绑定主配置项 配置数据属性
		ConfigDataProperties properties = binder.bind(NAME, BINDABLE_PROPERTIES, new ConfigDataLocationBindHandler())
				.orElse(null);

		// 🔄 如果存在 旧版 profile 配置，则将其注入到 配置数据属性 中
		if (!ObjectUtils.isEmpty(legacyProfiles)) {
			properties = (properties != null)
					? properties.withLegacyProfiles(legacyProfiles, legacyProfilesBindHandler.getProperty())
					: new ConfigDataProperties(null, new Activate(null, legacyProfiles));
		}
		return properties;
	}

	/**
	 * {@link BindHandler} 用于检查遗留的处理属性。
	 */
	private static class LegacyProfilesBindHandler implements BindHandler {

		private ConfigurationProperty property;

		@Override
		public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context,
				Object result) {
			this.property = context.getConfigurationProperty();
			return result;
		}

		ConfigurationProperty getProperty() {
			return this.property;
		}

	}

	/**
	 * 用于确定配置数据属性源何时激活的激活属性类。
	 */
	static class Activate {

		private final CloudPlatform onCloudPlatform;

		private final String[] onProfile;

		/**
		 * 创建一个新的 {@link Activate} 实例。
		 * @param onCloudPlatform 激活所需的云平台
		 * @param onProfile 激活所需的配置文件表达式数组
		 */
		Activate(CloudPlatform onCloudPlatform, String[] onProfile) {
			this.onProfile = onProfile;
			this.onCloudPlatform = onCloudPlatform;
		}

		/**
		 * 判断配置数据属性源是否对给定的激活上下文处于激活状态。
		 * @param activationContext 激活上下文
		 * @return 如果配置数据属性源处于激活状态则返回 {@code true}
		 */
		boolean isActive(ConfigDataActivationContext activationContext) {
			if (activationContext == null) {
				return false;
			}
			boolean activate = true;
			activate = activate && isActive(activationContext.getCloudPlatform());
			activate = activate && isActive(activationContext.getProfiles());
			return activate;
		}

		private boolean isActive(CloudPlatform cloudPlatform) {
			return this.onCloudPlatform == null || this.onCloudPlatform == cloudPlatform;
		}

		private boolean isActive(Profiles profiles) {
			return ObjectUtils.isEmpty(this.onProfile)
					|| (profiles != null && matchesActiveProfiles(profiles::isAccepted));
		}

		private boolean matchesActiveProfiles(Predicate<String> activeProfiles) {
			return org.springframework.core.env.Profiles.of(this.onProfile).matches(activeProfiles);
		}

	}

}
